package com.epidemy
import com.epidemy.actors._
import scala.actors.remote.Node
import java.util.Date
import collection.mutable.ArrayBuffer
import util.SerializationUtils
import java.net.{URLClassLoader, InetAddress, URL}
import java.io.FileOutputStream
import org.teleal.cling.model.message.UpnpResponse
import org.teleal.cling.model.action.ActionInvocation
import org.teleal.cling.support.igd.GetExternalIPCallback
import org.teleal.cling.model.types.UDAServiceId

/**
 * URL classloading :
 * http://www.szegedi.org/articles/remotejars.html
 *
 * 
 */
object Grid {
  val serializer = SerializationUtils

  case class GridNode(node: Node, localName: Symbol) {
    def exec(code: => Unit) = {
      localNodeActor ! RunProgram(localNodeActor.thisNode, newProgram(() => code))
    }
    def describe = localName.name + "@" + node.address + ":" + node.port
  }


  case class Topology(nodes: List[(GridNode, Date)])

  case class Ping(sourceNode: GridNode)
  case class Pong(sourceNode: GridNode)

  case object RequestTopologyUpdate
  case class TopologyUpdate(sender: GridNode, topology: Topology, requestReply: Boolean)
  case class ConnectTo(node: GridNode)
  case class DisconnectFrom(node: GridNode)

  case class RunProgram(sourceNode: GridNode, program: Program)
  case class Program(code: Array[Byte], classLoaderURL: URL) {
    def prepareDeserialization = {
      val deserializer = serializer.deserializer(code, new URLClassLoader(Array(classLoaderURL))) 
      () => deserializer().asInstanceOf[() => Unit]
    }
  }
  case class RemoteProcess(node: GridNode, id: Long)
  case class KillProcess(id: Long)
  
  def newProgram(action: () => Unit) =
    Program(serializer.serialize(action), classServerURL)
  
  var localAddress = //"127.0.0.1"
    InetAddress.getLocalHost.getCanonicalHostName
  
  lazy val classServerURL = {
    val s = new ClassServer
    new URL("http://" + localAddress + ":" + s.port.get)
  }
  
  private val localNodeNames = new ArrayBuffer[Symbol]()
  def ithName(i: Int) = scala.Symbol("GridNode" + i)
  def firstName = ithName(0)

  def newNode = localNodeNames synchronized {
    val newName = ithName(localNodeNames.size)
    val node = new NodeActor(GridNode(LocalRemoteActors.localNode, newName))
    localNodeNames += newName
    node.start
    node
  }
  lazy val localNodeActor = newNode

  def start = localNodeActor
  def main(args: Array[String]): Unit = {
/*
    //InternetGatewayDevice
    val service = device.findService(new UDAServiceId("WANIPConnection"));

    upnpService.getControlPoint().execute(
      new GetExternalIPCallback(service) {

          override def success(externalIPAddress: String): Unit = {
              //assertEquals(externalIPAddress, "123.123.123.123");
            println("externalIPAddress = " + externalIPAddress)
          }

          override def failure(invocation: ActionInvocation, operation: UpnpResponse): Unit = {
              // Something is wrong
          }
      }
    )
*/

    var remoteAddrs = new ArrayBuffer[Node]
    var rmtAdrRx = """([^:]+):(\d+)""".r
    def processArgs(as: List[String]): Unit = as match {
      case "-local" :: addr :: rest =>
        localAddress = addr
        processArgs(rest)
      case rmtAdrRx(remoteServer, remotePort) :: rest =>
        remoteAddrs += Node(remoteServer, remotePort.toInt)
        processArgs(rest)
      case Nil =>

      case unknown :: rest =>
        error("Unknown option : " + unknown)
    }

    processArgs(args.toList)
    
    start

    val name = firstName
    for (node <- remoteAddrs)
      localNodeActor ! ConnectTo(GridNode(node, name))

    if (args == Array("-demo")) {
      val extraNodes = 2
      for (i <- 0 until 2)//args.headOption.map(_.toInt).getOrElse(1) - 1)
        newNode

      var ns = nodes
      while (ns.size < 3) {
        //println("Got only " + ns.size + " nodes in topology... waiting for more !")
        Thread.sleep(1)
        ns = nodes
      }
      for (node <- ns) {
        println("Asking for execution on " + node)
        node exec {
          println("Hello world !")
          new FileOutputStream("test").close
          println("Wrote !")
        }
      }

      nodes
    }
  }
  
  def nodes = topology().map(_._1)
  
  def topology(includeThis: Boolean = true) = (localNodeActor !? NodeActor.GetTopology(includeThis)) match {
    case Topology(nodes) =>
      nodes
    case whatever: AnyRef =>
      error("Problem with local node, " + (if (whatever == null) "no reply" else "reply: " + whatever.getClass.getName + " = " + whatever))
  }
}